#include <vd2/system/memory.h>
#include <vd2/system/cpuaccel.h>
#include <vd2/Kasumi/pixmap.h>
#include <vd2/Kasumi/pixmaputils.h>

namespace {
	struct VDPixmapReferenceStretchBltParameters {
		void *dst;
		ptrdiff_t	dstpitch;
		const void *src;
		ptrdiff_t	srcpitch;
		ptrdiff_t	srcinc;
		sint32		dx;
		sint32		dy;
		uint32		u;
		uint32		uinc;
		uint32		dudx;
		uint32		v;
		uint32		dvdy;
		sint32		xprecopy;
		sint32		xpostcopy;
		ptrdiff_t	xprepos;
		ptrdiff_t	xpostpos;

		void advance() {
			dst = (char *)dst + dstpitch;
			src = (char *)src + srcinc;

			uint32 vt = v + dvdy;

			if (vt < v)
				src = (char *)src + srcpitch;

			v = vt;
		}
	};
}

void VDPixmapStretchBlt_Any8_nearest_reference(VDPixmapReferenceStretchBltParameters params) {
	do {
		uint8 *dstp = (uint8 *)params.dst;
		const uint8 *srcp = (const uint8 *)params.src;
		uint32 u = params.u;

		if (params.xprecopy) {
			VDMemset8(dstp, *(const uint8 *)((const char *)params.src + params.xprepos), params.xprecopy);
			dstp += params.xprecopy;
		}

		sint32 wt = params.dx;

		if (wt > 0)
			do {
				*dstp++ = *srcp;

				uint32 ut = u + params.dudx;
				srcp += ut<u;
				srcp += params.uinc;
				u = ut;
			} while(--wt);

		if (params.xpostcopy)
			VDMemset8(dstp, *(const uint8 *)((const char *)params.src + params.xpostpos), params.xpostcopy);

		params.advance();
	} while(--params.dy);
}

void VDPixmapStretchBlt_Any16_nearest_reference(VDPixmapReferenceStretchBltParameters params) {
	do {
		uint16 *dstp = (uint16 *)params.dst;
		const uint16 *srcp = (const uint16 *)params.src;
		uint32 u = params.u;

		if (params.xprecopy) {
			VDMemset16(dstp, *(const uint16 *)((const char *)params.src + params.xprepos), params.xprecopy);
			dstp += params.xprecopy;
		}

		sint32 wt = params.dx;

		if (wt > 0)
			do {
				*dstp++ = *srcp;

				uint32 ut = u + params.dudx;
				srcp += ut<u;
				srcp += params.uinc;
				u = ut;
			} while(--wt);

		if (params.xpostcopy)
			VDMemset16(dstp, *(const uint16 *)((const char *)params.src + params.xpostpos), params.xpostcopy);

		params.advance();
	} while(--params.dy);
}

void VDPixmapStretchBlt_Any24_nearest_reference(VDPixmapReferenceStretchBltParameters params) {
	do {
		uint8 *dstp = (uint8 *)params.dst;
		const uint8 *srcp = (const uint8 *)params.src;
		uint32 u = params.u;

		if (params.xprecopy) {
			const uint8 *repsrc = (const uint8 *)params.src + params.xprepos;
			const uint8 p0 = repsrc[0];
			const uint8 p1 = repsrc[1];
			const uint8 p2 = repsrc[2];

			for(sint32 i=0; i<params.xprecopy; ++i) {
				dstp[0] = p0;
				dstp[1] = p1;
				dstp[2] = p2;
				dstp += 3;
			}
		}

		sint32 wt = params.dx;

		if (wt > 0)
			do {
				dstp[0] = srcp[0];
				dstp[1] = srcp[1];
				dstp[2] = srcp[2];
				dstp += 3;

				uint32 ut = u + params.dudx;
				srcp += (ut<u)*3;
				srcp += params.uinc*3;
				u = ut;
			} while(--wt);

		if (params.xpostcopy) {
			const uint8 *repsrc = (const uint8 *)params.src + params.xpostpos;
			const uint8 p0 = repsrc[0];
			const uint8 p1 = repsrc[1];
			const uint8 p2 = repsrc[2];

			for(sint32 i=0; i<params.xpostcopy; ++i) {
				dstp[0] = p0;
				dstp[1] = p1;
				dstp[2] = p2;
				dstp += 3;
			}
		}

		params.advance();
	} while(--params.dy);
}

void VDPixmapStretchBlt_Any32_nearest_reference(VDPixmapReferenceStretchBltParameters params) {
	do {
		uint32 *dstp = (uint32 *)params.dst;
		const uint32 *srcp = (const uint32 *)params.src;
		uint32 u = params.u;

		if (params.xprecopy) {
			VDMemset32(dstp, *(const uint32 *)((const char *)params.src + params.xprepos), params.xprecopy);
			dstp += params.xprecopy;
		}

		sint32 wt = params.dx;
		if (wt > 0)
			do {
				*dstp++ = *srcp;

				uint32 ut = u + params.dudx;
				srcp += ut<u;
				srcp += params.uinc;
				u = ut;
			} while(--wt);

		if (params.xpostcopy)
			VDMemset32(dstp, *(const uint32 *)((const char *)params.src + params.xpostpos), params.xpostcopy);

		params.advance();
	} while(--params.dy);
}

///////////////////////////////////////////////////////////////////////////

namespace {
	void VDSetupNearestSamplingParameters(sint64& u64, sint64 dudx, sint32 dx, sint32 du, sint32& xprecopy, sint32& xprepos, sint32& xmain, sint32& xpostcopy, sint32& xpostpos) {
		sint64 ulo = u64;
		sint64 uhi = u64 + dudx * (dx - 1);
		sint64 tdudx = dudx;
		const sint64 ulimit = ((sint64)du << 32);

		xprepos = 0;
		xpostpos = du-1;

		if (!tdudx) {
			if (u64 < 0)
				xprecopy = dx;
			else if (u64 >= ulimit)
				xprecopy = dx;
			else
				xmain = dx;
		} else {
			if (tdudx < 0) {
				std::swap(ulo, uhi);
				tdudx = -tdudx;
			}

			if (ulo < 0) {
				if (uhi < 0)
					xprecopy = dx;
				else
					xprecopy = (sint32)((-ulo-1) / tdudx) + 1;

				VDASSERT(xprecopy <= 0 || (uint64)ulo >= (uint64)ulimit);
				VDASSERT(xprecopy <= 0 || (uint64)(ulo + tdudx * (xprecopy-1)) >= (uint64)ulimit);
			}

			if (uhi >= ulimit) {
				if (ulo >= ulimit)
					xpostcopy = dx;
				else
					xpostcopy = (sint32)((uhi - ulimit) / tdudx) + 1;

				VDASSERT(xpostcopy <= 0 || (uint64)uhi >= (uint64)ulimit);
				VDASSERT(xpostcopy <= 0 || (uint64)(uhi - tdudx * (xpostcopy - 1)) >= (uint64)ulimit);
			}

			if (dudx < 0) {
				std::swap(xprecopy, xpostcopy);
				std::swap(xprepos, xpostpos);
			}

			xmain = dx - (xprecopy + xpostcopy);
		}

		// sanity-check parameters

		VDASSERT(xprecopy>=0 && xprecopy <= dx);
		VDASSERT(xpostcopy>=0 && xpostcopy <= dx);
		VDASSERT(xmain>=0 && xmain <= dx);

		VDASSERT(xprecopy <= 0 || (uint64)u64 >= (uint64)ulimit);
		VDASSERT(xprecopy <= 0 || (uint64)(u64 + dudx * (xprecopy-1)) >= (uint64)ulimit);
		VDASSERT(xmain <= 0 || (uint64)(u64 + dudx * xprecopy) < (uint64)ulimit);
		VDASSERT(xmain <= 0 || (uint64)(u64 + dudx * (xprecopy+xmain-1)) < (uint64)ulimit);
		VDASSERT(xpostcopy <= 0 || (uint64)(u64 + dudx * (xprecopy + xmain)) >= (uint64)ulimit);
		VDASSERT(xpostcopy <= 0 || (uint64)(u64 + dudx * (xprecopy + xmain + xpostcopy - 1)) >= (uint64)ulimit);

		u64 += dudx * xprecopy;
	}
}

bool VDPixmapStretchBltNearest_reference(const VDPixmap& dst, sint32 x1, sint32 y1, sint32 x2, sint32 y2, const VDPixmap& src, sint32 u1, sint32 v1, sint32 u2, sint32 v2) {
	// we don't support format conversion
	if (dst.format != src.format)
		return false;

	void (*pBlitter)(VDPixmapReferenceStretchBltParameters);
	int bpp;

	switch(src.format) {
	case nsVDPixmap::kPixFormat_Pal8:
		pBlitter = VDPixmapStretchBlt_Any8_nearest_reference;
		bpp = 1;
		break;
	case nsVDPixmap::kPixFormat_XRGB1555:
	case nsVDPixmap::kPixFormat_RGB565:
		pBlitter = VDPixmapStretchBlt_Any16_nearest_reference;
		bpp = 2;
		break;
	case nsVDPixmap::kPixFormat_RGB888:
		pBlitter = VDPixmapStretchBlt_Any24_nearest_reference;
		bpp = 3;
		break;
	case nsVDPixmap::kPixFormat_XRGB8888:
		pBlitter = VDPixmapStretchBlt_Any32_nearest_reference;
		bpp = 4;
		break;
	default:
		return false;
	}

	// preemptive clip to prevent gradient calculations from crashing
	if (x2 == x1 || y2 == y1)
		return true;

	// translate destination flips into source flips
	if (x1 > x2) {
		std::swap(x1, x2);
		std::swap(u1, u2);
	}

	if (y1 > y2) {
		std::swap(y1, y2);
		std::swap(v1, v2);
	}

	// compute gradients
	sint32 dx	= x2 - x1;
	sint32 dy	= y2 - y1;
	sint32 du	= u2 - u1;
	sint32 dv	= v2 - v1;
	sint64 dudx = ((sint64)du << 32) / dx;		// must truncate toward zero to prevent overflow
	sint64 dvdy = ((sint64)dv << 32) / dy;

	// prestep top-left point to pixel center and convert destination coordinates to integer
	sint64 u64 = (sint64)u1 << 16;
	sint64 v64 = (sint64)v1 << 16;
	sint32 prestepx = (0x8000 - x1) & 0xffff;
	sint32 prestepy = (0x8000 - y1) & 0xffff;

	u64 += (dudx * prestepx) >> 16;
	v64 += (dvdy * prestepy) >> 16;

	sint32 x1i = (x1 + 0x8000) >> 16;
	sint32 y1i = (y1 + 0x8000) >> 16;
	sint32 x2i = (x2 + 0x8000) >> 16;
	sint32 y2i = (y2 + 0x8000) >> 16;

	// destination clipping
	if (x1i < 0) {
		u64 -= dudx * x1i;
		x1i = 0;
	}

	if (y1i < 0) {
		v64 -= dvdy * y1i;
		y1i = 0;
	}

	if (x2i > dst.w)
		x2i = dst.w;

	if (y2i > dst.h)
		y2i = dst.h;

	if (x1i >= x2i || y1i >= y2i)
		return true;

	// Calculate horizontal clip parameters
	sint32 xprecopy = 0, xpostcopy = 0;
	int xprepos = 0;
	int xpostpos = src.w-1;
	int xmain = 0;

	VDSetupNearestSamplingParameters(u64, dudx, x2i-x1i, src.w, xprecopy, xprepos, xmain, xpostcopy, xpostpos);

	// Calculate vertical clip parameters
	sint32 yprecopy = 0, ypostcopy = 0;
	int yprepos = 0;
	int ypostpos = src.h-1;
	int ymain = 0;

	VDSetupNearestSamplingParameters(v64, dvdy, y2i-y1i, src.h, yprecopy, yprepos, ymain, ypostcopy, ypostpos);

	// set up parameter block
	VDPixmapReferenceStretchBltParameters params;

	char *srcbase = (char *)src.data + (sint32)(u64 >> 32) * bpp;

	params.dst			= (char *)dst.data + y1i * dst.pitch + x1i * bpp;
	params.dstpitch		= dst.pitch;
	params.src			= srcbase + (sint32)(v64 >> 32) * src.pitch;
	params.srcpitch		= src.pitch;
	params.srcinc		= (sint32)(dvdy >> 32) * src.pitch;
	params.dx			= xmain;
	params.dy			= ymain;
	params.u			= (uint32)u64;
	params.uinc			= (uint32)(dudx >> 32);
	params.dudx			= (uint32)dudx;
	params.v			= (uint32)v64;
	params.dvdy			= (uint32)dvdy;
	params.xprecopy		= xprecopy;
	params.xprepos		= (xprepos - (sint32)(u64 >> 32)) * bpp;
	params.xpostcopy	= xpostcopy;
	params.xpostpos		= (xpostpos - (sint32)(u64 >> 32)) * bpp;

	if (yprecopy > 0) {
		VDPixmapReferenceStretchBltParameters preparams(params);

		preparams.src		= srcbase + yprepos * src.pitch;
		preparams.srcinc	= 0;
		preparams.dy		= yprecopy;
		preparams.v			= 0;
		preparams.dvdy		= 0;

		pBlitter(preparams);

		params.dst		= (char *)params.dst + params.dstpitch * yprecopy;
	}

	if (ymain > 0)
		pBlitter(params);

	if (ypostcopy > 0) {
		VDPixmapReferenceStretchBltParameters postparams(params);

		postparams.dst		= (char *)params.dst + params.dstpitch * params.dy;
		postparams.src		= srcbase + ypostpos * src.pitch;
		postparams.srcpitch	= 0;
		postparams.srcinc	= 0;
		postparams.dy		= ypostcopy;
		postparams.v		= 0;
		postparams.dvdy		= 0;

		pBlitter(postparams);
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////

namespace {
	uint32 lerp_XRGB1555(sint32 a, sint32 b, sint32 f) {
		sint32 a_rb	= a & 0x7c1f;
		sint32 a_g	= a & 0x03e0;
		sint32 b_rb	= b & 0x7c1f;
		sint32 b_g	= b & 0x03e0;

		const sint32 rb = (a_rb + (((b_rb - a_rb)*f + 0x4010) >> 5)) & 0x7c1f;
		const sint32 g  = (a_g  + (((b_g  - a_g )*f + 0x0200) >> 5)) & 0x03e0;

		return rb + g;
	}

	uint32 lerp_XRGB8888(sint32 a, sint32 b, sint32 f) {
		sint32 a_rb	= a & 0xff00ff;
		sint32 a_g	= a & 0x00ff00;
		sint32 b_rb	= b & 0xff00ff;
		sint32 b_g	= b & 0x00ff00;

		const uint32 rb = (a_rb + (((b_rb - a_rb)*f + 0x00800080) >> 8)) & 0xff00ff;
		const uint32 g  = (a_g  + (((b_g  - a_g )*f + 0x00008000) >> 8)) & 0x00ff00;

		return rb + g;
	}

	uint32 bilerp_RGB888(sint32 a, sint32 b, sint32 c, sint32 d, sint32 x, sint32 y) {
		sint32 a_rb	= a & 0xff00ff;
		sint32 a_g	= a & 0x00ff00;
		sint32 b_rb	= b & 0xff00ff;
		sint32 b_g	= b & 0x00ff00;
		sint32 c_rb	= c & 0xff00ff;
		sint32 c_g	= c & 0x00ff00;
		sint32 d_rb	= d & 0xff00ff;
		sint32 d_g	= d & 0x00ff00;

		const uint32 top_rb = (a_rb + (((b_rb - a_rb)*x + 0x00800080) >> 8)) & 0xff00ff;
		const uint32 top_g  = (a_g  + (((b_g  - a_g )*x + 0x00008000) >> 8)) & 0x00ff00;
		const uint32 bot_rb = (c_rb + (((d_rb - c_rb)*x + 0x00800080) >> 8)) & 0xff00ff;
		const uint32 bot_g  = (c_g  + (((d_g  - c_g )*x + 0x00008000) >> 8)) & 0x00ff00;

		const uint32 final_rb = (top_rb + (((bot_rb - top_rb)*y) >> 8)) & 0xff00ff;
		const uint32 final_g  = (top_g  + (((bot_g  - top_g )*y) >> 8)) & 0x00ff00;

		return final_rb + final_g;
	}

	uint32 bilerp_XRGB1555(sint32 a, sint32 b, sint32 c, sint32 d, sint32 x, sint32 y) {
		sint32 a_rb	= a & 0x7c1f;
		sint32 a_g	= a & 0x03e0;
		sint32 b_rb	= b & 0x7c1f;
		sint32 b_g	= b & 0x03e0;
		sint32 c_rb	= c & 0x7c1f;
		sint32 c_g	= c & 0x03e0;
		sint32 d_rb	= d & 0x7c1f;
		sint32 d_g	= d & 0x03e0;

		const sint32 top_rb = (a_rb + (((b_rb - a_rb)*x + 0x4010) >> 5)) & 0x7c1f;
		const sint32 top_g  = (a_g  + (((b_g  - a_g )*x + 0x0200) >> 5)) & 0x03e0;
		const sint32 bot_rb = (c_rb + (((d_rb - c_rb)*x + 0x4010) >> 5)) & 0x7c1f;
		const sint32 bot_g  = (c_g  + (((d_g  - c_g )*x + 0x0200) >> 5)) & 0x03e0;

		const sint32 final_rb = (top_rb + (((bot_rb - top_rb)*y + 0x4010) >> 5)) & 0x7c1f;
		const sint32 final_g  = (top_g  + (((bot_g  - top_g )*y + 0x0200) >> 5)) & 0x03e0;

		return final_rb + final_g;
	}

	uint32 bilerp_RGB565(sint32 a, sint32 b, sint32 c, sint32 d, sint32 x, sint32 y) {
		sint32 a_rb	= a & 0xf81f;
		sint32 a_g	= a & 0x07e0;
		sint32 b_rb	= b & 0xf81f;
		sint32 b_g	= b & 0x07e0;
		sint32 c_rb	= c & 0xf81f;
		sint32 c_g	= c & 0x07e0;
		sint32 d_rb	= d & 0xf81f;
		sint32 d_g	= d & 0x07e0;

		const sint32 top_rb = (a_rb + (((b_rb - a_rb)*x + 0x8010) >> 6)) & 0xf81f;
		const sint32 top_g  = (a_g  + (((b_g  - a_g )*x + 0x0400) >> 6)) & 0x07e0;
		const sint32 bot_rb = (c_rb + (((d_rb - c_rb)*x + 0x8010) >> 6)) & 0xf81f;
		const sint32 bot_g  = (c_g  + (((d_g  - c_g )*x + 0x0400) >> 6)) & 0x07e0;

		const sint32 final_rb = (top_rb + (((bot_rb - top_rb)*y + 0x8010) >> 6)) & 0xf81f;
		const sint32 final_g  = (top_g  + (((bot_g  - top_g )*y + 0x0400) >> 6)) & 0x07e0;

		return final_rb + final_g;
	}
}

///////////////////////////////////////////////////////////////////////////

namespace {
	struct VDPixmapReferenceStretchBltBilinearParameters {
		void		*dst;
		const void	*src;
		uint32		u;
		uint32		uinc;
		uint32		dudx;

		ptrdiff_t	xprepos;
		ptrdiff_t	xpostpos;
		sint32		xprecopy;
		sint32		xpostcopy;
		sint32		xmidsize;
	};

	void VDPixmapStretchBiH_XRGB1555_to_XRGB1555(const VDPixmapReferenceStretchBltBilinearParameters& params) {
		uint16 *dst = (uint16 *)params.dst;
		const uint16 *src = (const uint16 *)params.src;

		if (params.xprecopy)
			VDMemset16(dst - params.xprecopy, *(const uint16 *)((const char *)params.src + params.xprepos), params.xprecopy);

		if (params.xmidsize) {
			sint32 w = params.xmidsize;
			uint32 u = params.u;
			const uint32 dudx = params.dudx;
			const ptrdiff_t uinc = params.uinc;

			do {
				*dst++ = lerp_XRGB1555(src[0], src[1], u >> 27);

				const uint32 ut = u + dudx;
				src += uinc + (ut < u);
				u = ut;
			} while(--w);
		}

		if (params.xpostcopy)
			VDMemset16(dst, *(const uint16 *)((const char *)params.src + params.xpostpos), params.xpostcopy);
	}

	void VDPixmapStretchBiH_XRGB8888_to_XRGB8888(const VDPixmapReferenceStretchBltBilinearParameters& params) {
		uint32 *dst = (uint32 *)params.dst;
		const uint32 *src = (const uint32 *)params.src;

		if (params.xprecopy)
			VDMemset32(dst - params.xprecopy, *(const uint32 *)((const char *)params.src + params.xprepos), params.xprecopy);

		if (params.xmidsize) {
			sint32 w = params.xmidsize;
			uint32 u = params.u;
			const uint32 dudx = params.dudx;
			const ptrdiff_t uinc = params.uinc;

			do {
				*dst++ = lerp_XRGB8888(src[0], src[1], u >> 24);

				const uint32 ut = u + dudx;
				src += uinc + (ut < u);
				u = ut;
			} while(--w);
		}

		if (params.xpostcopy)
			VDMemset32(dst, *(const uint32 *)((const char *)params.src + params.xpostpos), params.xpostcopy);
	}

	void VDPixmapStretchBiV_XRGB1555_to_XRGB1555(void *dstv, const void *src1v, const void *src2v, sint32 w, uint32 f) {
		uint16 *dst = (uint16 *)dstv;
		const uint16 *src1 = (const uint16 *)src1v;
		const uint16 *src2 = (const uint16 *)src2v;

		f >>= 27;

		do {
			*dst++ = lerp_XRGB1555(*src1++, *src2++, f);
		} while(--w);
	}

	void VDPixmapStretchBiV_XRGB8888_to_XRGB8888(void *dstv, const void *src1v, const void *src2v, sint32 w, uint32 f) {
		uint32 *dst = (uint32 *)dstv;
		const uint32 *src1 = (const uint32 *)src1v;
		const uint32 *src2 = (const uint32 *)src2v;

		f >>= 24;

		do {
			*dst++ = lerp_XRGB8888(*src1++, *src2++, f);
		} while(--w);
	}
}

#ifdef _M_IX86
extern "C" void vdasm_stretchbltH_XRGB8888_to_XRGB8888_MMX(const VDPixmapReferenceStretchBltBilinearParameters&);

extern "C" void vdasm_stretchbltV_XRGB1555_to_XRGB1555_MMX(void *dstv, const void *src1v, const void *src2v, sint32 w, uint32 f);
extern "C" void vdasm_stretchbltV_XRGB8888_to_XRGB8888_MMX(void *dstv, const void *src1v, const void *src2v, sint32 w, uint32 f);
#endif

bool VDPixmapStretchBltBilinear_reference(const VDPixmap& dst, sint32 x1, sint32 y1, sint32 x2, sint32 y2, const VDPixmap& src, sint32 u1, sint32 v1, sint32 u2, sint32 v2) {
	// preemptive clip to prevent gradient calculations from crashing
	if (x2 == x1 || y2 == y1)
		return true;

	// we don't support source clipping
	if ((uint32)u1 > (uint32)(src.w << 16) || (uint32)v1 > (uint32)(src.h << 16))
		return false;

	if ((uint32)u2 > (uint32)(src.w << 16) || (uint32)v2 > (uint32)(src.h << 16))
		return false;

	// we don't support format changes (yet)
	if (dst.format != src.format)
		return false;

	// format determination
	void (*pHorizontalFilter)(const VDPixmapReferenceStretchBltBilinearParameters& params);
	void (*pVerticalFilter)(void *dstv, const void *src1v, const void *src2v, sint32 w, uint32 f);
	int bpp;

#pragma vdpragma_TODO("fixme this is b0rken")
	switch(src.format) {
	case nsVDPixmap::kPixFormat_XRGB1555:
		pHorizontalFilter = VDPixmapStretchBiH_XRGB1555_to_XRGB1555;
#ifdef _M_IX86
		if (CPUGetEnabledExtensions() & CPUF_SUPPORTS_MMX)
			pVerticalFilter = vdasm_stretchbltV_XRGB1555_to_XRGB1555_MMX;
		else
#endif
			pVerticalFilter = VDPixmapStretchBiV_XRGB1555_to_XRGB1555;
		bpp = 2;
		break;
	case nsVDPixmap::kPixFormat_XRGB8888:
#ifdef _M_IX86
		if (CPUGetEnabledExtensions() & CPUF_SUPPORTS_MMX) {
			pHorizontalFilter = vdasm_stretchbltH_XRGB8888_to_XRGB8888_MMX;
			pVerticalFilter = vdasm_stretchbltV_XRGB8888_to_XRGB8888_MMX;
		} else
#endif
		{
			pHorizontalFilter = VDPixmapStretchBiH_XRGB8888_to_XRGB8888;
			pVerticalFilter = VDPixmapStretchBiV_XRGB8888_to_XRGB8888;
		}
		bpp = 4;
		break;
	default:
		return false;
	}

	// translate destination flips into source flips
	if (x1 > x2) {
		std::swap(x1, x2);
		std::swap(u1, u2);
	}

	if (y1 > y2) {
		std::swap(y1, y2);
		std::swap(v1, v2);
	}

	// compute gradients
	sint32 dx	= x2 - x1;
	sint32 dy	= y2 - y1;
	sint32 du	= u2 - u1;
	sint32 dv	= v2 - v1;
	sint64 dudx = ((sint64)du << 32) / dx;		// must truncate toward zero to prevent overflow
	sint64 dvdy = ((sint64)dv << 32) / dy;

	// prestep top-left point to pixel center and convert destination coordinates to integer
	sint64 u64 = (sint64)u1 << 16;
	sint64 v64 = (sint64)v1 << 16;
	sint32 prestepx = (0x8000 - x1) & 0xffff;
	sint32 prestepy = (0x8000 - y1) & 0xffff;

	u64 += (dudx * prestepx) >> 16;
	v64 += (dvdy * prestepy) >> 16;

	sint32 x1i = (x1 + 0x8000) >> 16;
	sint32 y1i = (y1 + 0x8000) >> 16;
	sint32 x2i = (x2 + 0x8000) >> 16;
	sint32 y2i = (y2 + 0x8000) >> 16;

	// destination clipping
	if (x1i < 0) {
		u64 -= dudx * x1i;
		x1i = 0;
	}

	if (y1i < 0) {
		v64 -= dvdy * y1i;
		y1i = 0;
	}

	if (x2i > dst.w)
		x2i = dst.w;

	if (y2i > dst.h)
		y2i = dst.h;

	if (x1i >= x2i || y1i >= y2i)
		return true;

	u64 -= 0x80000000;
	v64 -= 0x80000000;

	int xprepos = 0;
	int xpostpos = src.w-1;

	sint64 ulo = u64;
	sint64 uhi = u64 + dudx * (x2i - x1i - 1);
	sint64 tdudx = dudx;

	if (ulo > uhi) {
		std::swap(ulo, uhi);
		tdudx = -tdudx;
	}

	int xprecopy = 0;
	int xpostcopy = 0;

	if (ulo < 0) {
		xprecopy = (int)((1 - ulo) / tdudx) + 1;
	}

	const sint64 ulimit = ((sint64)(src.w-1) << 32);

	if (uhi >= ulimit)
		xpostcopy = (int)((uhi - ulimit - 1) / tdudx) + 1;

	if (dudx < 0) {
		std::swap(xprecopy, xpostcopy);
		std::swap(xprepos, xpostpos);
	}

	u64 += dudx * xprecopy;
	const int xtotal	= x2i - x1i;
	int xmidcopy = (x2i - x1i) - (xprecopy + xpostcopy);
	const sint32 ui = (sint32)(u64 >> 32);

	// set up parameter block

	VDPixmapReferenceStretchBltBilinearParameters params;

	params.u			= (uint32)u64;
	params.uinc			= (sint32)(dudx >> 32);
	params.dudx			= (sint32)dudx;
	params.xprecopy		= xprecopy;
	params.xprepos		= (xprepos - ui) * bpp;
	params.xpostcopy	= xpostcopy;
	params.xpostpos		= (xpostpos - ui) * bpp;
	params.xmidsize		= xmidcopy;

	void *dstp			= (char *)dst.data + y1i * dst.pitch + x1i * bpp;
	const void *srcp	= (char *)src.data + ui * bpp;

	VDPixmapBuffer		window(xtotal, 2, src.format);

	void *pTempRow1 = window.data;
	void *pTempRow2 = (char *)window.data + window.pitch;
	int windowbottom = dvdy > 0 ? -0x7fffffff : 0x7fffffff;

	do {
		sint32 iv = (sint32)(v64 >> 32);
		sint32 iv_bottom = iv + 1;

		if (iv < 0)
			iv = iv_bottom = 0;

		if (iv >= src.h-1)
			iv = iv_bottom = src.h-1;

		if (dvdy < 0) {
			if (windowbottom > iv_bottom+1)
				windowbottom = iv_bottom+1;

			while(windowbottom > iv) {
				std::swap(pTempRow1, pTempRow2);

				--windowbottom;

				params.dst		= (char *)pTempRow1 + bpp * params.xprecopy;
				params.src		= vdptroffset(srcp, windowbottom * src.pitch);

				pHorizontalFilter(params);
			}
		} else {
			if (windowbottom < iv-1)
				windowbottom = iv-1;

			while(windowbottom < iv_bottom) {
				std::swap(pTempRow1, pTempRow2);

				++windowbottom;

				params.dst		= (char *)pTempRow2 + bpp * params.xprecopy;
				params.src		= vdptroffset(srcp, windowbottom * src.pitch);

				pHorizontalFilter(params);
			}
		}

		if (iv == iv_bottom)
			if (dvdy < 0)
				pVerticalFilter(dstp, pTempRow1, pTempRow1, xtotal, 0);
			else
				pVerticalFilter(dstp, pTempRow2, pTempRow2, xtotal, 0);
		else
			pVerticalFilter(dstp, pTempRow1, pTempRow2, xtotal, (uint32)v64);

		v64 += dvdy;
		dstp = (char *)dstp + dst.pitch;
	} while(++y1i < y2i);

	return true;
}
